#include "EstrategiaAlmacenamientoBloques.h"
#include "../recursoDeAlmacenamiento/RecursoDeAlmacenamiento.h"
#include "../FuncionesUtiles.h"
#include "../componentes/Bloque.h"
#include "../componentes/Registro.h"
#include "IteradorBloques.h"
#include "../componentes/RegistroVariable.h"
#include "../recursoDeAlmacenamiento/FabricaCompuestos.h"

#include <iostream>
using namespace std;

EstrategiaAlmacenamientoBloques::EstrategiaAlmacenamientoBloques(
		cantBytes tamanioBloque,FabricaCompuestos* fabrica):EstrategiaAlmacenamiento(fabrica) {
	this->tamanioHeader = LONGITUD_HEADER_BLOQUES;
	this->tamanioHeaderConPadding = tamanioBloque;
	this->tipoArchivo = ARCH_BLOQUES_REGISTROS_VAR;
	this->tamanioMetadata = LONGITUD_METADATA_BLOQUES;
	this->tamanioBloque = tamanioBloque;
	this->recurso = NULL;
}

EstrategiaAlmacenamientoBloques::~EstrategiaAlmacenamientoBloques() {
}

bool EstrategiaAlmacenamientoBloques::guardar(const Bytes& dato,long& numeroComponente) {

	RegistroVariable* registroAGuardar = new RegistroVariable(dato);

	// Trato de leer el bloque anterior para poder meter el registro adentro
	Bytes bytesBloque;
	Bloque* bloque = this->recurso->fabrica->crearCompuesto(this->tamanioBloque);

	offset offsetAEscribir;
	if (numeroComponente == -1) {
		offsetAEscribir = recurso->getTamanioArchivo();
	} else {
		offsetAEscribir = numeroComponente*this->tamanioBloque + this->tamanioHeaderConPadding;
	}

	// si se guardo algun bloque trato de meter el registro en el ultimo bloque
	long aux = offsetAEscribir - tamanioBloque;
	offset offsetBloqueAnterior = (aux > 0) ? aux: 0;
	if (offsetBloqueAnterior >= this->tamanioHeaderConPadding) {
		bytesBloque = recurso->leer(offsetBloqueAnterior, tamanioBloque);
		bloque->hidratarse(bytesBloque);
		if (bloque->tieneLugar(registroAGuardar)) {
			offsetAEscribir -= tamanioBloque;
		} else {
			delete bloque;
			bloque = new Bloque(this->tamanioBloque);
		}
	}

	if (bloque->tieneLugar(registroAGuardar)) {
		bloque->agregarRegistro(registroAGuardar);
	} else {
		cerr << "El tamanio del dato es muy grando como para entrar en el bloque" << endl;
		return false;
	}

	Bytes bloqueSerializado(bloque->serializarse());
	recurso->escribir(bloqueSerializado, offsetAEscribir);
	numeroComponente = (offsetAEscribir - this->tamanioHeaderConPadding) / this->tamanioBloque;
	delete bloque;

	return true;
}

void EstrategiaAlmacenamientoBloques::completarHeader(Bytes& header) {
	cantBytes tamanioBloque = this->tamanioBloque;
	header.agregarAlFinal((void*) &tamanioBloque, sizeof(tamanioBloque));
	header.agregarAlFinal(crearBytesNulos(this->tamanioHeaderConPadding - this->tamanioHeader));
}

bool EstrategiaAlmacenamientoBloques::headerCorrupto() {

	Bytes header = recurso->leer(0, this->tamanioHeader);

	if (header.getTamanio() < this->tamanioHeader) {
		return true;
	}

	if (header.getSubBytes(0, 1).toString() != this->tipoArchivo) {
		return true;
	}

	if (bytesToCantBytes(header.getSubBytes(1, 4)) != this->tamanioBloque) {
		return true;
	}

	return false;
}

Iterador* EstrategiaAlmacenamientoBloques::getIterador() {

	return new IteradorBloques(this);
}

Bytes EstrategiaAlmacenamientoBloques::obtener(offset numeroComponente, Key clave,TipoClave* extractor) {

	offset offsetInicial = numeroComponente * this->tamanioBloque + tamanioHeaderConPadding;
	Bytes bloqueSerializado = this->recurso->leer(offsetInicial,this->tamanioBloque);

	Bloque bloque(this->tamanioBloque);
	bloque.hidratarse(bloqueSerializado);

	return bloque.obtener(clave.serializarse(),extractor);
}

bool EstrategiaAlmacenamientoBloques::eliminar(offset numeroComponente, Key clave, TipoClave* extractor) {

	offset offsetInicial = numeroComponente * this->tamanioBloque + tamanioHeaderConPadding;
	Bytes bloqueSerializado = this->recurso->leer(offsetInicial,this->tamanioBloque);

	Bloque bloque(this->tamanioBloque);

	bloque.hidratarse(bloqueSerializado);

	bool exito = bloque.eliminar(clave.serializarse(),extractor);

	if (exito) {
		this->recurso->escribir(bloque.serializarse(),offsetInicial);
	}

	return exito;
}

